{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LX9Nj4MW7988"
      },
      "source": [
        "# The Qmod Workshop - Introduction\n",
        "\n",
        "The Classiq platform features a high-level quantum modeling language called Qmod. Qmod is compiled into concrete gate-level implementation using a powerful synthesis engine that optimizes and adapts the implementation to different target hardware/simulation environments.\n",
        "\n",
        "In this workshop, we will learn how to write quantum models using Qmod. We will be using the Python embedding of Qmod, available as part of the Classiq Python SDK. We will learn basic concepts in the Qmod language, such as functions, operators, quantum variables, and quantum types. We will develop useful building blocks and small algorithms.\n",
        "\n",
        "The [QMOD language reference](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/) covers these concepts more systematically and includes more examples.\n",
        "\n",
        "This workshop consists of step-by-step exercises. It is structured as follows:\n",
        "\n",
        "- Part 1: Language Fundamentals - Exercises 1-5\n",
        "- Part 2: Higher-Level Concepts - Exercises 6-10\n",
        "- Part 3: Execution Flows - Exercises 11, 12\n",
        "\n",
        "The introduction and Part 1 are included in this notebook. Part 2 and 3 are each in its own separate notebook. For each exercise you will find the solution to the exercises at the bottom of the same notebook.\n",
        "\n",
        "### Preparations\n",
        "\n",
        "Make sure you have a Python version of 3.8 through 3.11 installed. Unfortunately, Classiq is not yet supported with Python 3.12.\n",
        "\n",
        "Install Classiq’s Python SDK by following the instructions on this page: [Getting Started - Classiq](https://docs.classiq.io/latest/getting-started/).\n",
        "\n",
        "### Python Qmod Exercises - General Instructions\n",
        "\n",
        "In order to synthesize and execute your Qmod code, you should:\n",
        "1. Make sure you define a `main` function that calls functions you create.\n",
        "2. Use `create_model` by running `qmod = create_model(main)` to construct a representation of your model.\n",
        "3. You can synthesize the model (using `qprog = synthesize(qmod)`) to obtain an implementation - a quantum program.\n",
        "4. You can then visualize the quantum program (`show(qprog)`) or execute it (using `execute(qprog)`. See: [Execution - Classiq](https://docs.classiq.io/latest/user-guide/platform/executor/#full-example)). You can also execute it with the IDE after visualizing the circuit.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wiUgAF6b799A"
      },
      "source": [
        "### Exercise 0: From Model to Execution\n",
        "\n",
        "The following model defines a function that applies X and H gates on a single qubit, and subsequently calls it:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "collapsed": true,
        "id": "rVXqqX_29sJl",
        "outputId": "bbb97ef1-d1c8-4fb8-e261-cdf2835b4933"
      },
      "outputs": [],
      "source": [
        "!pip install Classiq -U"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "metadata": {
        "id": "aW7zH-kF_E7I"
      },
      "outputs": [],
      "source": [
        "import classiq\n",
        "classiq.authenticate()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "metadata": {
        "id": "PZqYYahm799B"
      },
      "outputs": [],
      "source": [
        "from classiq import *\n",
        "\n",
        "\n",
        "# Define a quantum function using the @qfunc decorator\n",
        "@qfunc\n",
        "def foo(q: QBit) -> None:\n",
        "    X(target=q)\n",
        "    H(target=q)\n",
        "\n",
        "\n",
        "# Define a main function\n",
        "@qfunc\n",
        "def main(res: Output[QBit]) -> None:\n",
        "    allocate(1, res)\n",
        "    foo(q=res)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "JcaIP1z4799D"
      },
      "source": [
        "Create a model from it, and synthesize, visualize, and execute it.\n",
        "\n",
        "Use the General Instructions above to do so.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Ddd9pkX4799D",
        "outputId": "1a7f0313-8e94-448d-bb70-e9d687bd9213"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/a7f8d3ed-b5a9-4ea8-bb51-8e3ce0d91a45?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "#from classiq import *\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kXLBnCL7799D"
      },
      "source": [
        "In Qmod `QBit` is the simplest quantum type, and in this example, `q` is a quantum variable of type `QBit`. Quantum variables abstract away the mapping of quantum objects to qubits in the actual circuit.\n",
        "\n",
        "See also [Quantum Variables](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/quantum-variables/).\n",
        "\n",
        "We will discuss other quantum types during the workshop.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "lfR9qo6w799E"
      },
      "source": [
        "# The Qmod Workshop - Part 1: Language Fundamentals\n",
        "\n",
        "Follow exercises 1 through 5 for the first session of the workshop."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "rtn2t2Ss799E"
      },
      "source": [
        "## Exercise 1 - Bell Pair\n",
        "\n",
        "Create a function that takes two single-qubit (`QBit`) quantum arguments and prepares the bell state on them ([Bell state](https://en.wikipedia.org/wiki/Bell_state)) by applying `H` on one variable and then using it as the control of a `CX` function with the second variable as the target.\n",
        "Create a main function that uses this function and has two single-qubit outputs, initialize them to the |0> state (using the `allocate` function), and apply your function to them.\n",
        "\n",
        "See also [Functions](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/functions#syntax)\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "metadata": {
        "id": "PCKbZcmz799E"
      },
      "outputs": [],
      "source": [
        "#from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def Bell_state(reg: QArray[QBit]) -> None:\n",
        "  H(reg[0])\n",
        "  CX(reg[0], reg[1])\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "KftDIAch799F"
      },
      "source": [
        "Use qubit array subscript (the syntax - _variable_ **[** _index-expression_ **]**) to change the function from subsection 1 to receive a single quantum variable, a qubit array (`QArray`) of size 2.\n",
        "Change your main function to declare a single output (also an array of size 2).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "2sSaN_rb799G",
        "outputId": "e487b48b-1962-4b30-dda9-b4628319a6b0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/b41d29b4-6c0f-4755-8fe4-eeef655d90c8?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "from classiq import *\n",
        "\n",
        "\n",
        "\n",
        "@qfunc\n",
        "def main(reg: Output[QArray]) -> None:\n",
        "  allocate(2, reg)\n",
        "  Bell_state(reg)\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "9MZU1trw799G"
      },
      "source": [
        "## Exercise 2 - Repeat\n",
        "\n",
        "Use the built-in `repeat` operator to create your own Hadamard transform function (call it `my_hadamard_transform`). The Hadamard transform function is a function that takes as argument a qubit array of an unspecified size and applies `H` to each of its qubit.\n",
        "\n",
        "See also [Classical repeat](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/classical-control-flow/#classical-repeat).\n",
        "\n",
        "Set your main function to have a quantum array output of unspecified size, allocate 10 qubits, and then apply your Hadamard transform function.\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "7oWqiQZc799H",
        "outputId": "7dd0102f-2dbd-42a0-d113-d4eb6a95b63d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/98f614f3-51d8-448e-ac3c-8255a03e1101?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def my_hadamard_transform(q: QArray[QBit]) -> None:\n",
        "  repeat(q.len, lambda i: H(q[i]))\n",
        "\n",
        "@qfunc\n",
        "def main(reg: Output[QArray]):\n",
        "    allocate(10,reg)\n",
        "    my_hadamard_transform(reg)\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EIP8M24s799I"
      },
      "source": [
        "### Note: Quantum Variable Capture\n",
        "The `repeat` operator invokes a statement block multiple times. The statement block is specified using a Python callable, typically a lambda expression. Inside the block you can refer to variables declared in the outer function scope.\n",
        "This concept is called `quantum variable capture`, equivalent to [capture](https://en.wikipedia.org/wiki/Closure_(computer_programming)) in classical languages.\n",
        "\n",
        "See also [Capturing context variables and parameters](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/operators/#capturing-context-variables-and-parameters)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EInvZN2C799I"
      },
      "source": [
        "### Exercise 3 - Power\n",
        "Raising a quantum operation to a power appears in many known algorithms, for examples, in Grover search and Quantum Phase Estimation.\n",
        "For most operations, it simply means repeating the same circuit multiple times.\n",
        "\n",
        "Sometimes, however, power can be simplified, thus saving computational resources.\n",
        "The most trivial example is a quantum operation expressed as a single explicit unitary matrix (i.e., all n*n matrix terms are given) -  raising the operation can be done by raising the matrix to that power via classical programming.\n",
        "\n",
        "See also [Power operator](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/quantum-operators/#syntax).\n",
        "\n",
        "Use the following code to generate a 2-qubit (real) unitary matrix:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 25,
      "metadata": {
        "id": "KQ0U7_-X799I"
      },
      "outputs": [],
      "source": [
        "from typing import List\n",
        "\n",
        "import numpy as np\n",
        "\n",
        "from classiq import *\n",
        "\n",
        "rng = np.random.default_rng(seed=0)\n",
        "random_matrix = rng.random((4, 4))\n",
        "qr_unitary, _ = np.linalg.qr(random_matrix)\n",
        "\n",
        "unitary_matrix = QConstant(\"unitary_matrix\", List[List[float]], qr_unitary.tolist())"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "kDFpeA9U799I"
      },
      "source": [
        "In order to reuse some classical value we can define a `QConstant` to store that value.\n",
        "\n",
        "1. Create a model that applies `unitary_matrix` on a 2 qubit variable.\n",
        "2. Create another model that applies `unitary_matrix` raised to power 3 on a 2 qubit variable.\n",
        "3. Compare the gate count via the Classiq’s IDE in both cases.\n",
        "\n",
        "Note - the signature of function `unitary` is:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "metadata": {
        "id": "ylzmS1JQ799J"
      },
      "outputs": [],
      "source": [
        "def unitary(\n",
        "    elements: CArray[CArray[CReal]],\n",
        "    target: QArray[QBit],\n",
        ") -> None:\n",
        "    pass"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "CVqGyIsG799J",
        "outputId": "8b802983-240d-4b79-879a-0b6757b4ded0"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/e1876e2a-9375-4304-bec6-0a3e8d3fea6c?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "#1. Create a model that applies unitary_matrix on a 2 qubit variable.\n",
        "from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def main(reg: Output[QArray]) -> None:\n",
        "  allocate(2, reg)\n",
        "  unitary(unitary_matrix,reg)\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)\n",
        "# Depth: 7\n",
        "# Width: 2\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "0vIfSXmZTGeZ",
        "outputId": "044d433c-5fe9-47c5-ff5e-fec3af4c022f"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/c8f3f1f4-e6b2-4db0-bc76-666c8734e47a?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "#2. Create another model that applies unitary_matrix raised to power 3 on a 2 qubit variable.\n",
        "from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def main(reg: Output[QArray]) -> None:\n",
        "  allocate(2, reg)\n",
        "  power(3, lambda :unitary(unitary_matrix,reg))\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)\n",
        "#Depth: 7\n",
        "#Width: 2"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "dtwwMQIZUXXs"
      },
      "outputs": [],
      "source": [
        "# 3. Compare the gate count via the Classiq’s IDE in both cases.\n",
        "# in both cases we had the same Depth and width"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XGGNdn2m799J"
      },
      "source": [
        "## Exercise 4 - User-defined Operators\n",
        "Create a function that applies a given single-qubit operation to all qubits in its quantum argument (Call your function `my_apply_to_all`). Such a function is also called an operator, i.e. a function that one of its arguments is another function (its operand).\n",
        "\n",
        "See also [Operators](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/operators/).\n",
        "\n",
        "Follow these guidelines:\n",
        "1. Your function should declare a quantum argument of type qubit array. It should also declare an argument of a function type with a single qubit argument.\n",
        "2. The body should apply the operand to all qubits in the argument.\n",
        "\n",
        "When you're done, re-implement `my_hadamard_transform` from exercise 2 using this function instead of `repeat`.\n",
        "Use the same main function from exercise 2."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "Fujf8Kj8799J",
        "outputId": "2b25d971-01f2-4c38-889b-fde6dd75e69e"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/dce195a0-6bd2-44f9-a684-031290c29da5?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def my_apply_to_all(reg: QArray[QBit], my_operand: QCallable[QBit]) -> None:\n",
        "  repeat(reg.len, lambda i: my_operand(reg[i]))\n",
        "\n",
        "\n",
        "@qfunc\n",
        "def main(reg: Output[QArray[QBit]]) -> None:\n",
        "  allocate(3, reg)\n",
        "  my_apply_to_all(reg, lambda q: X(q))\n",
        "\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "wRgnSqNn799J"
      },
      "source": [
        "# Exercise 5 -  Quantum Conditionals\n",
        "\n",
        "### Exercise 5a - Control Operator\n",
        "Use the built-in `control` operator to create a function that receives two single qubit variables and uses one of the variables to control an RY gate with a `pi/2` angle acting on the other variable (without using the `CRY` function).\n",
        "\n",
        "See also [Quantum operators](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/quantum-operators/#syntax).\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 39,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "yF2EPzzW799K",
        "outputId": "6be03fe4-0450-45ad-df63-1ce0775b1bc9"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/3949fcd7-7692-4c9e-a652-4d35b95b66cc?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "from classiq import *\n",
        "from sympy import pi\n",
        "@qfunc\n",
        "def my_control(ctr: QBit, target: QBit) -> None:\n",
        "  control(ctr, lambda: RY(pi/2, target))\n",
        "\n",
        "@qfunc\n",
        "def main(qreg: Output[QArray[QBit]]) -> None:\n",
        "    allocate(2, qreg)\n",
        "    #X(qreg[0])\n",
        "    my_control(qreg[0], qreg[1])\n",
        "\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LXja3yoA799K"
      },
      "source": [
        "\n",
        "\n",
        "```\n",
        "# This is formatted as code\n",
        "```\n",
        "\n",
        "### Exercise 5b - Control (\"Quantum If\")\n",
        "The `control` operator is the conditional application of some operation, with the condition being that all control qubits are in the state |1>. This notion is generalized in QMOD to other control states, where the condition is specified as a comparison between a quantum numeric variable and a numeric value, similar to a classical `if` statement. Quantum numeric variables are declared with class `QNum`.\n",
        "\n",
        "See also [Numeric types](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/quantum-types/#syntax).\n",
        "\n",
        "In QMOD this generalization is available as a native statement - control.\n",
        "\n",
        "See also [control](https://docs.classiq.io/latest/user-guide/platform/qmod/language-reference/statements/quantum-operators/).\n",
        "\n",
        "1. Declare a `QNum` output argument using `Output[QNum]` and name it `x`.\n",
        "2. Use the `prepare_int` function to initialize it to `9`. Note that you don't need to specify the `QNum` attributes - size, sign, and fraction digits, as they are inferred at the point of initialization.\n",
        "3. Execute the circuit and observe the results.\n",
        "4. Declare another output argument of type `QBit` and perform a `control` such that under the condition that `x` is 9, the qubit is flipped. Execute the circuit and observe the results. Repeat for a different condition."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 40,
      "metadata": {
        "colab": {
          "base_uri": "https://localhost:8080/"
        },
        "id": "AdiivlfV799K",
        "outputId": "6324e711-60bb-457c-dee2-37f072b2625d"
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Opening: https://platform.classiq.io/circuit/5492ad49-897e-4dc8-aa7b-4d02792f66ca?version=0.41.1\n"
          ]
        }
      ],
      "source": [
        "from classiq import *\n",
        "\n",
        "@qfunc\n",
        "def main(x: Output[QNum], y: Output[QBit]) -> None:\n",
        "  prepare_int(9, x)\n",
        "  allocate(1, y)\n",
        "  control(x == 9, lambda: X(y))\n",
        "\n",
        "qmod = create_model(main)\n",
        "qprog = synthesize(qmod)\n",
        "show(qprog)"
      ]
    }
  ],
  "metadata": {
    "colab": {
      "provenance": []
    },
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.10.9"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
